package com.itcam.project.service.impl;

import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itcam.iahcommon.constant.CommonConstant;
import com.itcam.iahcommon.enums.ErrorCode;
import com.itcam.iahcommon.exception.BusinessException;
import com.itcam.iahcommon.exception.ThrowUtils;
import com.itcam.iahcommon.model.dto.generator.GeneratorCacheRequest;
import com.itcam.iahcommon.model.dto.generator.GeneratorMakeRequest;
import com.itcam.iahcommon.model.dto.generator.GeneratorQueryRequest;
import com.itcam.iahcommon.model.dto.generator.GeneratorUseRequest;
import com.itcam.iahcommon.model.entity.Generator;
import com.itcam.iahcommon.model.entity.User;
import com.itcam.iahcommon.model.vo.GeneratorVO;
import com.itcam.iahcommon.model.vo.UserVO;
import com.itcam.iahcommon.response.BaseResponse;
import com.itcam.iahcommon.utils.ResultUtils;
import com.itcam.iahcommon.utils.SqlUtils;
import com.itcam.maker.generator.main.GenerateTemplate;
import com.itcam.maker.generator.main.ZipGenerator;
import com.itcam.maker.meta.Meta;
import com.itcam.maker.meta.MetaValidator;
import com.itcam.project.manager.CacheManager;
import com.itcam.project.manager.CosManager;
import com.itcam.project.mapper.GeneratorMapper;
import com.itcam.project.service.GeneratorService;
import com.itcam.project.service.UserService;
import com.qcloud.cos.model.COSObject;
import com.qcloud.cos.model.COSObjectInputStream;
import com.qcloud.cos.utils.IOUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author Cammy
 * @description 针对表【generator(代码生成器)】的数据库操作Service实现
 * @createDate 2025-02-12 22:51:27
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class GeneratorServiceImpl extends ServiceImpl<GeneratorMapper, Generator>
        implements GeneratorService {

    private final UserService userService;

    private final CosManager cosManager;

    private final CacheManager cacheManager;

    /**
     * 验证生成器对象的有效性
     *
     * 🕵️‍♂️ 这个方法就像一位超级严谨的侦探，专门负责审查生成器对象是否合格。它会依据传入的生成器对象以及一个布尔值，来精准地对生成器对象进行验证。
     *
     * @param generator 要验证的生成器对象，好比是等待侦探审查的嫌疑人👨‍🦲
     * @param add 一个布尔值，用于指定验证的某些条件（具体条件需根据业务逻辑确定），就像是侦探手中的特殊审查规则手册📖
     * @throws BusinessException 如果验证失败，侦探会立刻吹响警报🚨，抛出包含错误码的业务异常
     */
    @Override
    public void validGenerator(Generator generator, boolean add) {
        // 👀 侦探首先会仔细查看嫌疑人是否存在，如果嫌疑人都没影，那可不行，直接判定为参数错误，抛出异常
        if (null == generator) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 📋 侦探从嫌疑人身上取出名字和描述信息，准备进行详细调查
        String name = generator.getName();
        String description = generator.getDescription();

        // 🆕 如果是创建新对象（add 为 true），就像是嫌疑人初次登场，名字和描述可不能是空的
        if (add) {
            // 🔍 侦探会仔细检查名字和描述，如果有任何一个是空的，就判定违反规则，抛出参数错误异常
            ThrowUtils.throwIf(StringUtils.isAnyBlank(name, description), ErrorCode.PARAMS_ERROR);
        }
        // 👀 侦探接着会查看名字，如果名字不为空，就会检查它是不是太长了
        if (StringUtils.isNotBlank(name) && name.length() > 80) {
            // ⚠️ 哎呀，名字太长了，不符合要求，侦探果断判定参数错误，抛出异常并提示名字太长
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "名称过长");
        }
        // 📝 同样，侦探会检查描述，如果描述不为空，也会查看它是否过长
        if (StringUtils.isNotBlank(description) && description.length() > 256) {
            // ⚠️ 糟糕，描述也太长了，侦探再次判定参数错误，抛出异常并提示描述太长
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "描述过长");
        }
    }


    /**
     * 根据生成器查询请求构建查询条件包装器
     *
     * @param generatorQueryRequest 生成器查询请求对象
     * @return 查询条件包装器
     */
    @Override
    public QueryWrapper<Generator> getQueryWrapper(GeneratorQueryRequest generatorQueryRequest) {
        QueryWrapper<Generator> queryWrapper = new QueryWrapper<>();
        if (generatorQueryRequest == null) {
            return queryWrapper;
        }
        String searchText = generatorQueryRequest.getSearchText();
        String sortField = generatorQueryRequest.getSortField();
        String sortOrder = generatorQueryRequest.getSortOrder();
        Long id = generatorQueryRequest.getId();

        String name = generatorQueryRequest.getName();
        String description = generatorQueryRequest.getDescription();
        List<String> tagList = generatorQueryRequest.getTags();
        Integer status = generatorQueryRequest.getStatus();

        Long userId = generatorQueryRequest.getUserId();
        Long notId = generatorQueryRequest.getNotId();
        // 拼接查询条件
        if (StringUtils.isNotBlank(searchText)) {
            queryWrapper.like("name", searchText).or().like("description", searchText);
        }
        queryWrapper.like(StringUtils.isNotBlank(name), "name", name);
        queryWrapper.like(StringUtils.isNotBlank(description), "description", description);
        if (CollUtil.isNotEmpty(tagList)) {
            for (String tag : tagList) {
                queryWrapper.like("tags", "\"" + tag + "\"");
            }
        }
        queryWrapper.eq(ObjectUtils.isNotEmpty(status), "status", status);
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取生成器对象的VO（视图对象）
     *
     * @param generator 生成器对象
     * @param request HTTP请求对象
     * @return 生成器对象的VO
     */
    @Override
    public GeneratorVO getGeneratorVO(Generator generator, HttpServletRequest request) {
        // 将生成器对象转换为VO对象
        GeneratorVO generatorVO = GeneratorVO.objToVo(generator);
        // 获取生成器的ID
        long generatorId = generator.getId();
        // 1. 关联查询用户信息
        // 获取生成器关联的用户ID
        Long userId = generator.getUserId();
        User user = null;
        // 如果用户ID不为空且大于0，则查询用户信息
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        // 将用户对象转换为VO对象
        UserVO userVO = userService.getUserVO(user);
        // 将用户VO对象设置到生成器VO对象中
        generatorVO.setUser(userVO);
        // 返回生成器VO对象
        return generatorVO;
    }


    /**
     * 分页获取生成器对象封装
     * @param generatorPage
     * @param request
     * @return
     */
    @Override
    public Page<GeneratorVO> getGeneratorVOPage(Page<Generator> generatorPage, HttpServletRequest request) {
        List<Generator> generatorList = generatorPage.getRecords();
        Page<GeneratorVO> generatorVOPage = new Page<>(generatorPage.getCurrent(), generatorPage.getSize(), generatorPage.getTotal());
        if (CollUtil.isEmpty(generatorList)) {
            return generatorVOPage;
        }
        // 1. 关联查询用户信息
        Set<Long> userIdSet = generatorList.stream().map(Generator::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        // 填充信息
        List<GeneratorVO> generatorVOList = generatorList.stream().map(generator -> {
            GeneratorVO generatorVO = GeneratorVO.objToVo(generator);
            Long userId = generator.getUserId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            generatorVO.setUser(userService.getUserVO(user));
            return generatorVO;
        }).collect(Collectors.toList());
        generatorVOPage.setRecords(generatorVOList);
        return generatorVOPage;
    }

    /**
     * 使用代码生成器
     * @param generatorUseRequest
     * @param request
     * @param response
     */
    @Override
    public void useGenerator(GeneratorUseRequest generatorUseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 1.获取用户输入的请求参数
        Long id = generatorUseRequest.getId();
        Map<String, Object> dataModel = generatorUseRequest.getDataModel();

        // 2.需要用户登录
        User loginUser = userService.getLoginUser(request);
        log.info("userId = {} 使用了生成器 id = {}", loginUser.getId(), id);

        Generator generator = this.getById(id);
        if (null == generator) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }

        // 3.生成器的存储路径
        String distPath = generator.getDistPath();
        if (StrUtil.isBlank(distPath)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "产物包不存在");
        }

        // 4.从对象存储下载生成器的压缩包
        // 4.1 定义独立的工作空间
        // 获取当前项目的路径
        String projectPath = System.getProperty("user.dir");
        // 构造临时目录路径，用于存放下载的压缩包和生成的文件
        String tempDirPath = String.format("%s/.temp/use/%s", projectPath, id);
        // 构造压缩包文件路径
        String zipFilePath = tempDirPath + "/dish.zip";
        // 4.2 不存在，新建文件
        // 检查压缩包文件是否存在，如果不存在则创建
        if (!FileUtil.exist(zipFilePath)) {
            FileUtil.touch(zipFilePath);
        }

//        StopWatch stopWatch = new StopWatch();
//        stopWatch.start();
        // 4.3 下载文件
        try {
            // 调用cosManager的download方法下载生成器压缩包到指定路径
            cosManager.download(distPath, zipFilePath);
        } catch (InterruptedException e) {
            // 如果下载过程中发生中断异常，抛出业务异常
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成器下载失败");
        }
//        stopWatch.stop();
//        System.out.println("下载耗时：" + stopWatch.getTotalTimeMillis());

//        stopWatch = new StopWatch();
//        stopWatch.start();
        // 5.解压文件，得到用户上传的生成器文件
        // 使用ZipUtil工具类解压下载的压缩包
        File unzipped = ZipUtil.unzip(zipFilePath);
//        stopWatch.stop();
//        System.out.println("解压耗时：" + stopWatch.getTotalTimeMillis());

//        stopWatch = new StopWatch();
//        stopWatch.start();
        // 6.将用户输入的参数写入到JSON文件中
        // 构造数据模型文件路径
        String dataModelFilePath = tempDirPath + "/dataModel.json";
        // 将数据模型转换为JSON字符串
        String jsonStr = JSONUtil.toJsonStr(dataModel);
        // 将JSON字符串写入到文件中
        FileUtil.writeUtf8String(jsonStr, dataModelFilePath);
//        stopWatch.stop();
//        System.out.println("写数据文件耗时：" + stopWatch.getTotalTimeMillis());

        // 7.执行脚本
        // 7.1 找到脚本文件所在路径
        // 使用FileUtil的loopFiles方法遍历解压后的目录，查找名为generator.bat的文件
        File scriptFile = FileUtil.loopFiles(unzipped, 2, null)
                .stream()
                .filter(file -> file.isFile() && "generator.bat".equals(file.getName()))
                .findFirst()
                .orElseThrow(RuntimeException::new);

        // 7.2 添加可执行权限
        try {
            // 设置脚本文件的权限为可执行
            Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwxrwxrwx");
            Files.setPosixFilePermissions(scriptFile.toPath(), permissions);
        } catch (IOException e) {
            // 处理异常，例如记录日志或抛出更具体的异常
        }

        // 8.构造命令
        // 获取脚本文件所在的目录
        File scriptDir = scriptFile.getParentFile();
        // 获取脚本文件的绝对路径，并将路径中的反斜杠替换为正斜杠
        String scriptAbsolutePath = scriptFile.getAbsolutePath().replace("\\", "/");
        // 构造命令数组，包括脚本文件路径和参数
        String[] commands = {scriptAbsolutePath, "json-generate", "--file=" + dataModelFilePath};

        // 创建ProcessBuilder对象，用于执行命令
        ProcessBuilder processBuilder = new ProcessBuilder(commands);
        // 设置命令执行的工作目录
        processBuilder.directory(scriptDir);

        try {
//            stopWatch = new StopWatch();
//            stopWatch.start();
            // 启动命令进程
            Process process = processBuilder.start();
            // 获取命令的输出流
            InputStream inputStream = process.getInputStream();
            // 使用BufferedReader读取输出流
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            // 逐行读取输出并打印
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 等待命令执行完成，并获取退出码
            int exitCode = process.waitFor();
            System.out.println("命令执行结束，退出码：" + exitCode);

//            stopWatch.stop();
//            System.out.println("执行脚本耗时：" + stopWatch.getTotalTimeMillis());
        } catch (Exception e) {
            // 打印异常堆栈跟踪
            e.printStackTrace();
            // 抛出业务异常
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "执行生成器脚本错误");
        }

//        stopWatch = new StopWatch();
//        stopWatch.start();
        // 9.生成代码的位置
        // 构造生成代码的目录路径
        String generatedPath = scriptDir.getAbsolutePath() + "/generated";
        // 构造结果压缩包文件路径
        String resultPath = tempDirPath + "/result.zip";
        // 使用ZipUtil工具类将生成的代码目录压缩成zip文件
        File resultFile = ZipUtil.zip(generatedPath, resultPath);
//        stopWatch.stop();
//        System.out.println("压缩结果耗时：" + stopWatch.getTotalTimeMillis());

        // 10.下载文件
        // 10.1 设置响应头
        // 设置响应的内容类型为二进制流
        response.setContentType("application/octet-stream;charset=UTF-8");
        // 设置响应头，指定文件名，以便浏览器下载
        response.setHeader("Content-Disposition", "attachment; filename=" + resultFile.getName());
        // 10.2 写入响应
        // 将生成的压缩包文件写入到HTTP响应的输出流中
        Files.copy(resultFile.toPath(), response.getOutputStream());

        // 11.清理文件
        // 使用CompletableFuture异步执行文件清理操作
        CompletableFuture.runAsync(() -> {
            // 删除临时目录及其内容
            FileUtil.del(tempDirPath);
        });
    }

    /**
     * 缓存代码生成器
     * @param generatorCacheRequest
     * @param request
     * @param response
     */
    @Override
    public void cacheGenerator(GeneratorCacheRequest generatorCacheRequest, HttpServletRequest request, HttpServletResponse response) {
        if (null == generatorCacheRequest || generatorCacheRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 获取生成器
        Long requestId = generatorCacheRequest.getId();
        Generator generator = this.getById(requestId);
        if (null == generator) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }

        String distPath = generator.getDistPath();
        if (StrUtil.isBlank(distPath)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "产物包不存在");
        }

        // 缓存空间
        String zipFilePath = getCacheFilePath(requestId, distPath);

        // 新建文件
        if (!FileUtil.exist(zipFilePath)) {
            FileUtil.touch(zipFilePath);
        }

        // 下载生成器
        try {
            cosManager.download(distPath, zipFilePath);
        } catch (InterruptedException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "压缩包下载失败");
        }
    }

    /**
     * 根据生成器id下载生成器
     * @param id
     * @param request
     * @param response
     * @throws IOException
     */
    @Override
    public void downloadGeneratorById(long id, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 检查传入的ID是否合法，若小于等于0则抛出参数错误异常
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 获取当前登录的用户信息
        User loginUser = userService.getLoginUser(request);
        // 根据ID从数据库中获取生成器信息
        Generator generator = this.getById(id);
        // 检查生成器是否存在，若不存在则抛出未找到错误异常
        if (generator == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }

        // 获取生成器的产物包存储路径
        String dishPath = generator.getDistPath();
        // 检查产物包路径是否为空，若为空则抛出未找到错误异常并提示产物包不存在
        if (StrUtil.isBlank(dishPath)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "产物包不存在");
        }

        // 追踪事件
        log.info("用户 {} 下载了 {}", loginUser, dishPath);

        // 记录日志，追踪用户下载行为
        log.info("用户 {} 下载了 {}", loginUser, dishPath);
        // 设置响应头，指定响应内容类型为二进制流，并设置文件名以便客户端下载
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + dishPath);

        // 优先从缓存读取文件，根据ID和产物包路径获取缓存文件路径
        String zipFilePath = getCacheFilePath(id, dishPath);
        // 检查缓存文件是否存在
        if (FileUtil.exist(zipFilePath)) {
            // 若缓存文件存在，将缓存文件内容写入响应输出流
            Files.copy(Paths.get(zipFilePath), response.getOutputStream());
            return;
        }

        // 用于存储对象存储的输入流
        COSObjectInputStream cosObjectInput = null;
        try {
            // 创建一个计时器，用于记录下载耗时
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            // 从对象存储中获取指定路径的对象
            COSObject cosObject = cosManager.getObject(dishPath);
            // 获取对象的输入流
            cosObjectInput = cosObject.getObjectContent();
            // 将输入流中的数据转换为字节数组
            byte[] bytes = IOUtils.toByteArray(cosObjectInput);
            stopWatch.stop();
            // 打印下载耗时
            System.out.println(stopWatch.getTotalTimeMillis());
            // 再次设置响应头，确保响应内容类型和文件名正确
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + dishPath);
            // 将字节数组写入响应输出流
            response.getOutputStream().write(bytes);
            // 刷新输出流，确保数据全部发送
            response.getOutputStream().flush();
        } catch (Exception e) {
            // 记录下载错误日志
            log.error("file download error, filepath = " + dishPath, e);
            // 抛出系统错误异常，提示下载失败
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败");
        } finally {
            // 确保输入流在使用完毕后关闭
            if (null != cosObjectInput) {
                cosObjectInput.close();
            }
        }
    }

    /**
     * 制作代码生成器
     * 🏭【代码生成器工厂】生产线全流程控制中心
     * 实现原理：原料准备 → 下载模板 → 解压加工 → 生成产品 → 打包发货
     * 类比：汽车制造流水线（设计图 → 零件组装 → 质量检测 → 成品出厂）
     * @param generatorMakeRequest
     * @param request
     * @param response
     */
    @Override
    public void makeGenerator(GeneratorMakeRequest generatorMakeRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // ================== 🚩阶段1：原料质检 ==================
        // 1️⃣ 【订单接收】获取用户输入参数
        String zipFilePath = generatorMakeRequest.getZipFilePath(); // 📦 设计图纸地址
        if (StrUtil.isBlank(zipFilePath)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "压缩包不存在");
        }
        Meta meta = generatorMakeRequest.getMeta(); // 🏗️ 车型设计参数

        // 2️⃣ 【身份验证】必须登录才能使用生产线
        User loginUser = userService.getLoginUser(request);
        log.info("工程师 {} 启动生成器制作", loginUser.getId());

        // ================== 🚩阶段2：搭建车间 ==================
        // 3️⃣ 【创建专属车间】构建独立工作区（防止不同订单互相干扰）
        String projectPath = System.getProperty("user.dir");
        String workshopId = IdUtil.getSnowflakeNextId() + RandomUtil.randomString(6); // 🆔 车间唯一编号
        String tempWorkshopPath = String.format("%s/.temp/make/%s", projectPath, workshopId);
        String localZipPath = tempWorkshopPath + "/project.zip"; // 🧱 原料存放区

        // 新建文件
        if (!FileUtil.exist(localZipPath)) {
            FileUtil.touch(localZipPath);
        }

        // 4️⃣ 【原料接收】下载模板压缩包（类似物流车运送零件）
        if (!FileUtil.exist(localZipPath)) {
            FileUtil.touch(localZipPath); // 📦 创建零件暂存区
        }
        try {
            cosManager.download(zipFilePath, localZipPath); // 🚚 从中央仓库调货
        } catch (InterruptedException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "物流中断，零件运输失败");
        }

        // ================== 🚩阶段3：组装生产线 ==================
        // 5️⃣ 【拆包验货】解压模板文件（打开零件包装）
        File assemblyLine = ZipUtil.unzip(localZipPath);

        // 6️⃣ 【参数校准】验证设计图纸完整性
        meta.getFileConfig().setSourceRootPath(assemblyLine.getAbsolutePath());
        MetaValidator.doValidAndFill(meta); // 📐 质量检测员
        String outputPath = String.format("%s/generated/%s", tempWorkshopPath, meta.getName());

        // ================== 🚩阶段4：启动生产 ==================
        // 7️⃣ 【选择生产线】使用ZIP打包生成器（可扩展其他生成方式）
        GenerateTemplate productionLine = new ZipGenerator();
        try {
            productionLine.doGenerate(outputPath, meta); // 🏭 流水线启动
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "组装过程发生故障");
        }

        // ================== 🚩阶段5：成品出厂 ==================
        // 8️⃣ 【包装发货】生成最终产品包
        String suffix = "-dist.zip";
        String productName = meta.getName() + suffix;
        String finalProductPath = outputPath + suffix;

        // 9️⃣ 【物流配送】设置响应头（贴快递单）
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + productName);
        Files.copy(Paths.get(finalProductPath), response.getOutputStream());

        // 🔟 【车间回收】异步清理（自动扫地机器人）
        CompletableFuture.runAsync(() -> FileUtil.del(tempWorkshopPath));
    }

    /**
     * 📦【智能仓储系统】—— 分页查询优化版
     * 亮点功能：多级缓存加速 + 反爬虫机制 + 字段精简优化
     */
    @Override
    public BaseResponse<Page<GeneratorVO>> listGeneratorVOByPageFast(GeneratorQueryRequest generatorQueryRequest, HttpServletRequest request) {
        // 🚩阶段1：参数校验
        long current = generatorQueryRequest.getCurrent();
        long size = generatorQueryRequest.getPageSize();
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR, "检测到异常查询行为"); // 🛡️ 反爬虫盾牌

        // 🚩阶段2：缓存查询
        String cacheKey = getPageCacheKey(generatorQueryRequest);
        Object cacheData = cacheManager.get(cacheKey);
        if (cacheData != null) {
            return ResultUtils.success((Page<GeneratorVO>) cacheData); // ⚡ 闪电返回
        }

        // 🚩阶段3：数据库查询
        QueryWrapper<Generator> queryWrapper = this.getQueryWrapper(generatorQueryRequest);
        queryWrapper.select("id", "name", "description", "tags", "picture", "status",
                "userId", "createTime", "updateTime"); // 🎯 精准字段狙击

        Page<Generator> rawPage = page(new Page<>(current, size), queryWrapper);
        Page<GeneratorVO> voPage = getGeneratorVOPage(rawPage, request); // 🎨 数据美颜

        // 🚩阶段4：缓存写入
        cacheManager.put(cacheKey, JSONUtil.toJsonStr(voPage)); // 💾 存入知识库
        return ResultUtils.success(voPage);
    }

    /**
     * 获取分页缓存 key
     * 🔑【缓存密钥生成器】
     * 设计要点：Base64编码保障特殊字符安全 + 前缀隔离不同业务
     *
     * 该方法用于根据传入的 GeneratorQueryRequest 对象生成一个唯一的分页缓存键。
     * 具体步骤为：先将 GeneratorQueryRequest 对象转换为 JSON 字符串，
     * 然后对该 JSON 字符串进行 Base64 编码，最后将编码后的字符串与固定前缀 "generator:page" 拼接，
     * 得到最终的缓存键。这样可以确保不同的查询请求对应不同的缓存键，避免缓存冲突。
     *
     * @param generatorQueryRequest 生成器查询请求对象，包含分页和查询条件信息
     * @return 返回根据查询请求生成的唯一分页缓存键
     */
    public static String getPageCacheKey(GeneratorQueryRequest generatorQueryRequest) {
        // 将 GeneratorQueryRequest 对象转换为 JSON 字符串
        String jsonStr = JSONUtil.toJsonStr(generatorQueryRequest);
        // 对 JSON 字符串进行 Base64 编码
        String base64 = Base64Encoder.encode(jsonStr);
        // 拼接固定前缀和 Base64 编码后的字符串，生成最终的缓存键
        String key = "generator:page" + base64;
        // 返回生成的缓存键
        return key;
    }


    /**
     * 获取缓存文件路径
     *
     * @param id 生成器的 ID，用于标识不同的生成器实例
     * @param distPath 生成器的存储路径，指定了生成器文件在存储系统中的位置
     * @return 返回生成器缓存文件的完整路径
     */
    public String getCacheFilePath(long id, String distPath) {
        // 获取当前项目的路径
        String projectPath = System.getProperty("user.dir");
        // 构造临时目录路径，用于存放缓存文件
        String tempDirPath = String.format("%s/.temp/cache/%s", projectPath, id);
        // 构造缓存文件的完整路径
        String zipFilePath = String.format("%s/%s", tempDirPath, distPath);
        // 返回缓存文件的完整路径
        return zipFilePath;
    }


}




